﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Web.Mvc;
using BabyShop.Site.Data.Conventions;
using BabyShop.Site.Data.Conventions.Combines;
using BabyShop.Site.Data.Conventions.Properties;
using BabyShop.Site.Exts.Extensions;

namespace BabyShop.Site.Exts.ModelBinders
{
    public class QueryConditionExpressionModelBinder : IModelBinder
    {
        private readonly ConventionConfiguration _conventionConfiguration;

        public QueryConditionExpressionModelBinder(ConventionConfiguration conventionConfiguration) {
            _conventionConfiguration = conventionConfiguration;
        }

        public QueryConditionExpressionModelBinder() : this(ConventionConfiguration.Default) { }

        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
            var modelType = GetModelTypeFromExpressionType(bindingContext.ModelType);
            if (modelType == null) return null;

            var parameter = Expression.Parameter(modelType, modelType.Name[0].ToString().ToLower());

            var dict = new Dictionary<string, Expression>();
            var propertyExpressionConvernions = _conventionConfiguration.GetConventions<IPropertyExpressionConvention>();
            foreach (var property in modelType.GetProperties()) {
                foreach (var convention in propertyExpressionConvernions) {
                    var context = new BuildPropertyExpressionContext(
                        property,
                        bindingContext.ValueProvider,
                        controllerContext.Controller.ViewData.ModelState,
                        parameter.Property(property.Name)
                        );
                    var expression = convention.BuildExpression(context);
                    if (expression != null) {
                        dict.Add(property.Name, expression);
                        break;
                    }
                    if (context.IsHandled) break;
                }
            }
            var body = default(Expression);
            foreach (var convention in _conventionConfiguration.GetConventions<IExpressionCombineConvention>()) {
                body = convention.Combine(dict);
                if (body != null) break;
            }
            return body.ToLambda(parameter);
        }

        private static Type GetModelTypeFromExpressionType(Type lambdaExpressionType) {

            if (lambdaExpressionType.GetGenericTypeDefinition() != typeof(Expression<>)) return null;

            var funcType = lambdaExpressionType.GetGenericArguments()[0];
            if (funcType.GetGenericTypeDefinition() != typeof(Func<,>)) return null;

            var funcTypeArgs = funcType.GetGenericArguments();
            return funcTypeArgs[1] != typeof(bool) ? null : funcTypeArgs[0];
        }
    }
}